<?
require "Assert.php";
require "Test.php";

import('japha.lang.Class');
import('japha.lang.IllegalAccessException');
import('japha.lang.reflect.*');

/**
 * A test case defines the fixture to run multiple tests. To define a test case<br>
 *
 *     1) implement a subclass of TestCase<br>
 *     2) define instance variables that store the state of the fixture<br>
 *     3) initialize the fixture state by overriding <code>setUp</code><br>
 *     4) clean-up after a test by overriding <code>tearDown</code>.<br>
 *
 * Each test runs in its own fixture so there can be no side effects among test runs. Here is an example:
 *
 * <pre>
 * class MathTest extends TestCase 
 * { 
 *     protected $fValue1; 
 *     protected $fValue2;
 *
 *     protected function setUp() 
 *     {
 *         $this->fValue1= 2.0;
 *         $this->fValue2= 3.0;
 *     }
 * }
 * </pre>
 *
 * For each test implement a method which interacts with the fixture. Verify the expected results with assertions 
 * specified by calling <code>assertTrue</code> with a boolean.
 *
 * <pre>
 * public void testAdd() 
 * {
 *     $result = $this->fValue1 + $this->fValue2;
 *     assertTrue( $result == 5.0 );
 * }
 * </pre>
 *
 * Once the methods are defined you can run them. The framework supports both a static type safe and more dynamic 
 * way to run a test. In the static way you override the runTest method and define the method to be invoked. A 
 * convenient way to do so is with an anonymous inner class. (Note, anon inners are not available in PHP5).
 *
 * <pre>
 * $test = new MathTest("add") 
 * {
 *     public void runTest()
 *     {
 *         testAdd();
 *     }
 * };
 * $test->run();
 * </pre>
 *
 * The dynamic way uses reflection to implement <code>runTest</code>. It dynamically finds and invokes a method.
 * In this case the name of the test case has to correspond to the test method to be run.
 *
 * <pre>
 * TestCase = new MathTest("testAdd");
 * $test->run();
 * </pre>
 *
 * The tests to be run can be collected into a TestSuite. JUnit provides different <i>test runners</i> which can 
 * run a test suite and collect the results. A test runner either expects a static method <code>suite</code> as 
 * the entry point to get a test to run or it will extract the suite automatically.
 *
 * <pre>
 * public static function suite() 
 * {
 *     $suite = new TestSuite();
 *     $suite->addTest( new MathTest("testAdd") );
 *     $suite->addTest( new MathTest("testDivideByZero") );
 *     return $suite;
 * }
 * </pre>
 *
 * @see TestResult
 * @see TestSuite
 * @author Ryan Gantt
 */
abstract class TestCase extends Assert implements Test
{
	/**
	 * the name of the test case
	 *
	 * @type String
	 */
	private $fName;
	
	/**
	 * Constructs a test case with the given name.
	 *
	 * @param name The name of the test case to construct
	 */
	public function __construct( $name = null ) 
	{
		$this->fName = $name;
	}
	
	/**
	 * Counts the number of test cases executed by run( TestResult $result ).
	 *
	 * @return int 1
	 */
	public function countTestCases() 
	{
		return 1;
	}
	
	/**
	 * Creates a default TestResult object
	 *
	 * @see TestResult
	 * @return TestResult A new instance of TestResult
	 */
	protected function createResult() 
	{
		return new TestResult();
	}
	
	/**
	 * A convenience method to run this test, collecting the results with a
	 * default TestResult object.
	 *
	 * Runs the test case and collects the results in TestResult.
	 *
	 * @see TestResult
	 * @param result The instance of TestResult to run the "run()" method on
	 */
	public function run( TestResult $result = null ) 
	{
		if( $result == null )
		{
			$result = $this->createResult();
			$this->run( $result );
			return $result;
		}
		else
			$result->run( $this );
	}
	
	/**
	 * Runs the bare test sequence.
	 *
	 * @throws Throwable if any exception is thrown underneath is
	 */
	public function runBare()
	{
		$this->setUp();
		try 
		{
			$this->runTest();
		} 
		catch( Exception $e )
		{
			throw $e;   
		}
		$this->tearDown();
	}
	
	/**
	 * Override to run the test and assert its state.
	 *
	 * @throws Throwable if any exception is thrown
	 */
	protected function runTest()
	{
		Assert::assertNotNull( $this->fName );
		$runMethod = new Method( get_class( $this ), $this->fName );
		try 
		{
			// This gets a method directly from the top of the hierarchy (Object)
			//$runMethod = $this->getClass()->getMethod( $this->fName );
		} 
		catch ( Exception $e )
		{
			$this->fail('Method '.$this->fName.' not found');
		}
		if ( !Modifier::isPublic( $runMethod->getModifiers() ) ) 
			$this->fail('Method '.$this->fName.' should be public');
		try 
		{
			$runMethod->invoke( $this, new Vector() );
		}
		catch( InvocationTargetException $e ) 
		{
			$e->fillInStackTrace();
			throw $e->getTargetException();
		}
		catch( IllegalAccessException $e ) 
		{
			$e->fillInStackTrace();
			throw $e;
		}
	}
	
	/**
	 * Returns a string representation of the test case
	 *
	 * @return string A string representation of the test case
	 */
	public function toString() 
	{
		return $this->getName().'('.$this->getClass()->getName().')';
	}
	
	/**
	 * Gets the name of a TestCase
	 *
	 * @return string The name of the current TestCase
	 */
	public function getName() 
	{
		return $this->fName;
	}
	
	/**
	 * Sets the name of a TestCase
	 *
	 * @param name The name to associate with the current TestCase
	 */
	public function setName( $name ) 
	{
		$this->fName = $name;
	}
	
	/**
	 * Sets up the fixture, for example, open a network connection.
	 * This method is called before a test is executed.
	 */
	abstract protected function setUp();
	
	/**
	 * Tears down the fixture, for example, close a network connection.
	 * This method is called after a test is executed.
	 */
	abstract protected function tearDown();
}